package com.generalbackend.common.util;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;

/**
 * 雪花算法
 * Twitter_snowflake
 * 1.结构
 * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 0000000000 <br>
 * 1位标识，由于long基本类型在Java中是带符号的，最高位的是符号位，正数是0，负数是1，所有id一般是正数，最高位是0<br>
 * 41位时间戳（毫秒级），注意，41位时间戳不是存储当前时间的时间戳，而是存储时间戳的差值（当前时间戳-开始时间戳）得到的值），这里的开始时间戳，一般是我们的id生成器开始使用的时间，由我们程序来指定的（如下面程序IdWorker类的startTime属性）。
 * 41位时间戳，可以使用69年，年T=（1L<<41)/(1000L*60*60*24*365)=69<br>[可冗余记录当前时间]
 * 10位的数据机器位，可以部署在1024个节点，包括5位dataCenterId和workId<br>
 * 12位序列，毫秒内的计数，12位的计数顺序号支持每个节点每毫秒（同一机器，同一时间戳）参数4096个ID序号<br>
 * 加起来刚好64位，为一个Long型。<br>
 * SnowFlake的优点是，整体上按照时间自增排序，并且整个分布式系统内不会产生ID碰撞（由数据中心ID和机器ID做区分），并且效率较高，经测试，SnowFlake每秒能够产生26万ID左右。
 * @author tim.xie
 * @date 2021/11/17 16:36
 */
public class SnowFlakeIdGeneratorUtil {

    /**
     * 开始时间戳（2020-10-22 14：08：54）
     */
    private static final long twEpoch = 1603346934000L;

    /**
     * 某机房的某台机器id所占的位数
     */
    private static final long workerIdBits = 5L;

    /**
     * 数据中心（机房）id所占的位数
     */
    private static final long dataCenterIdBits = 5L;

    /**
     * 序列在id中占的位数
     */
    private static final long sequenceBits = 10L;

    /**
     * 最大位数尾位移5位，取异或获取最大值。最大的机器id
     */
    private static final long maxWorkId = ~(-1L << workerIdBits);

    /**
     * 最大数据标志，结果是31，2的5次方-1
     */
    private static final long maxDataCenterId = ~(-1L << dataCenterIdBits);

    /**
     * 最大的序列号，序列掩码4095（2的12次方-1）
     */
    private static final long sequenceMask = ~(-1L << sequenceBits);

    /**
     * 机器ID向左移12位（序列id占10位，左移12位后是机器ID）
     */
    private static final long workerIdShift = sequenceBits;

    /**
     * 数据（中心）ID向左移17位
     */
    private static final long dataCenterIdShift = sequenceBits + workerIdBits;

    /**
     * 时间戳差左移22位
     */
    private static final long timeStampLeftShift = sequenceBits + workerIdBits + dataCenterIdBits;

    /**
     * 工作机器ID（1～31）
     */
    private static long workId;

    /**
     * 数据（中心）ID（1～31）
     */
    private static long dataCenterId;

    /**
     * 毫秒内序列（0～4095）
     */
    private static long sequence = 0L;

    /**
     * 上次生成ID的时间戳
     */
    private static long lastTimestamp = -1L;

    /**
     * 位数长度限制，防止前端精度丢失
     */
    private static long lengthLimit = (1L << 52) - 1;

    /**
     * 初始化
     *
     * @param workerId
     * @param dataCenterId
     */
    public SnowFlakeIdGeneratorUtil(long workerId, long dataCenterId) {
        if (workerId > maxWorkId || workerId < 0) {
            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkId));
        }
        if (dataCenterId > maxDataCenterId || dataCenterId < 0) {
            throw new IllegalArgumentException(String.format("dataCenter Id can't be greater than %d or less than 0", maxDataCenterId));
        }
        this.workId = workerId;
        this.dataCenterId = dataCenterId;
    }

    public static synchronized long nextId() {
        long timestamp = timeGen();
        //如果当前时间小于上一次ID生成的时间戳，说明系统时间回退，生成id应为异常
        if (timestamp < lastTimestamp) {
            throw new RuntimeException(
                    String.format("Clock moved backwards.Refusing to generate id for %d milliseconds", lastTimestamp - timestamp)
            );
        }
        //如果是同一时间生成的，则进行毫秒内序列
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & sequenceMask;
            //毫秒内序列溢出
            if (sequence == 0) {
                //阻塞到下一个毫秒，获得新到时间戳
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }
        lastTimestamp = timestamp;
        return ((timestamp - twEpoch) << timeStampLeftShift) & lengthLimit
                | (dataCenterId << dataCenterIdShift)
                | (workId << workerIdShift)
                | sequence;
    }

    /**
     * 阻塞到下一个毫秒，知道获得新到时间戳
     *
     * @param lastTimestamp
     * @return
     */
    private static long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    /**
     * 获取当前时间
     *
     * @return
     */
    private static long timeGen() {
        return System.currentTimeMillis();
    }


    public static <T, R> List<R> bulkGenerateId(List<T> index, Function<T, R> f) {
        List<R> list = new ArrayList<>();
        index.forEach(item -> list.add(f.apply(item)));
        return list;
    }


    public static void main(String[] args) {
//        Set<Long> idSet = new HashSet<>();
//        for (int i = 0; i < 100000; i++) {
//            long id = nextId();
//            System.out.println(Long.toBinaryString(id));
//            idSet.add(id);
//        }
//        System.out.println(idSet.size());
        System.out.println(nextId());
    }


}
